/*  -*- mode: c++; coding: utf-8; c-file-style: "stroustrup"; -*-

    Copyright 2008 Asier Aguirre <asier.aguirre@gmail.com>
    This file is part of Intromit.

    Intromit is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Intromit is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Intromit.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef PYMETADATA_HH
#define PYMETADATA_HH
//! \defgroup python_backend Python backend
//@{

// INCLUDES
#include <Python.h>
#include "util.hh"

// DEFINES
#define PYMD_MAX_STATIC_METHODS    511
#define PYMD_GENERATE              FR0511
#define PYMD_PROTO_DECLARATION(i)  static PyObject *pyfunc##i(PyObject *self, PyObject *args);

// python2.3 compatibility issue
#ifndef Py_RETURN_NONE
#define Py_RETURN_NONE return Py_INCREF(Py_None), Py_None
#endif

// ENUMS
enum pymd_cobj_type
{
    PYMD_P1=0,
    PYMD_P2,
    PYMD_P3,
    PYMD_P4,
};

// STRUCTS
struct pymd_attr_member
{
    unsigned read_only:1;     // member will appear in python for read only
    unsigned nopickle:1;      // member will not be used in serialization (pickle/unpickle)
    unsigned priv:1;          // member will be prepend a '_'
    unsigned reserved:29;
};
struct pymd_attr_method
{
    unsigned prepickle:1;     // method will be called before a getstatus python internal function
    unsigned postunpickle:1;  // method will be called after a setstatus python internal function
    unsigned event_queue:1;   // when a postunpickle method is called, these methods are registered in event queue
    unsigned priv:1;          // method will be prepend a '_'
    unsigned nowrap:1;        // method will not be wrapped, but called directly
    unsigned reserved:27;
};

// DECLARATIONS
class python_type_metadata;
struct pymd_method_closure;

// CLASSES

/*! \brief Python object that holds a C++ object

   \verbatim
   Types of C++ objects by its contents
     |----+---------------------------------|
     | #  | Type of C++ object              |
     |----+---------------------------------|
     | C1 | C++ object with reference count |
     | C2 | C++ object w/o reference count  |
     |----+---------------------------------|

   Types of C++ objects by its allocation
     |----+--------------------------------------|
     | #  | Type of C++ allocation               |
     |----+--------------------------------------|
     | A1 | C++ object in heap (dynamic)         |
     | A2 | C++ object contained in other object |
     | A3 | C++ object in stack (forbiden)       |
     | A4 | C++ object global (TODO)             |
     |----+--------------------------------------|

   Types of python_metadata objects:
     |----+------------+------------------------+------------------------------------|
     | #  | C++        | Description            | Implementation                     |
     |----+------------+------------------------+------------------------------------|
     | P1 | C1A1       | C++/Python interface   | obj points to object,              |
     |    |            | objects with refcnt    | obj_container is NULL, C++ refcnt  |
     |    |            |                        |                                    |
     | P2 | C2A1       | Objects generated in   | obj points to object,              |
     |    |            | python w/o refcnt      | obj_container is NULL, no refcnt   |
     |    |            |                        |                                    |
     | P3 | C1A2, C2A2 | Objects contained in   | indirection=0, location is the     |
     |    |            | other objects          | offset of C++ obj in obj_container |
     |    |            |                        | obj_container is that of container |
     |    |            |                        |                                    |
     | P4 | C2A1+      | Pointer to objects w/o | indirection=1, location is the     |
     |    |            | refcnt in other objs   | offset of C++ obj pointer          |
     |    |            |                        | obj_container is that of container |
     |----+------------+------------------------+------------------------------------|

   Interoperativity C++ to python:
     |-------------+------------+------------+--------------+--------------|
     | From \ To   | P1         | P2         | P3           | P4           |
     |-------------+------------+------------+--------------+--------------|
     | C1A1        | C++ incref | -          | -            | -            |
     |             |            |            |              |              |
     | C2A1        | -          | Create     | -            | -            |
     |             |            | C++ object |              |              |
     |             |            |            |              |              |
     | C1A2, C2A2  | -          | -          | As described | -            |
     |             |            |            | above        |              |
     |             |            |            |              |              |
     | C2A1+       | -          | -          | -            | As described |
     |             |            |            |              | above        |
     |             |            |            |              |              |
     | Func RetVal | ?          | ?          | ?            | ?            |
     |-------------+------------+------------+--------------+--------------|

   Interoperativity python to C++:
     |-----------+-----------------+-------------------------------------+---------------------------------|
     | From \ To | C1A1            | C1A2, C2A2                          | C2A1+                           |
     |-----------+-----------------+-------------------------------------+---------------------------------|
     | P1        | Decref previous | If C refcnt is >1 then show error   | -                               |
     |           | C++ and incref  | and exit. If there is a python      |                                 |
     |           | new C++         | reference to contained object,      |                                 |
     |           |                 | extract it and convert P3 to P1     |                                 |
     |           |                 | else destruct it. Then copy dynamic |                                 |
     |           |                 | obj into contained one and convert  |                                 |
     |           |                 | P1 into P3, then free w/o dtor the  |                                 |
     |           |                 | dynamic obj.                        |                                 |
     |           |                 |                                     |                                 |
     | P2        | -               | Same as with P1 omitting            | If there is a python ref to C   |
     |           |                 | refcnt condition                    | obj, convert from P4 to P2 else |
     |           |                 |                                     | must suppose that actual C++    |
     |           |                 |                                     | referenced obj is not being     |
     |           |                 |                                     | referenced from other places,   |
     |           |                 |                                     | so delete it. Set P2 obj and    |
     |           |                 |                                     | convert P2 into P4.             |
     |           |                 |                                     |                                 |
     | P3        | Not possible    | If there is a py-ref to contained   | Not possible                    |
     |           |                 | obj, extract and convert P3 to P1   |                                 |
     |           |                 | else destruct it. Then copy new obj |                                 |
     |           |                 | from old container to new one and   |                                 |
     |           |                 | repoint original P3 to new C locat. |                                 |
     |           |                 | Finally call ctor on old C position |                                 |
     |           |                 |                                     |                                 |
     | P4        | -               | Not possible                        | Not possible                    |
     |-----------+-----------------+-------------------------------------+---------------------------------|
   \endverbatim
*/
class python_metadata
{
    // python object header (must be placed at class start)
    PyObject_HEAD

    union
    {
	// real C++ object to be wrapped if obj_container==NULL
	void *obj;

	// container member if obj_container!=NULL
	struct {unsigned location:31, indirection:1;};
    };

    // container python metadata (NULL implies free reference-counted object)
    python_metadata *obj_container;

    // PRIVATE FUNCTIONS
    python_metadata();
    ~python_metadata();

public:

    // alloc
    // if a C++ object python reference already exists
    //     it increases python count and returns same python reference
    // otherwise
    //     it creates a new python reference;
    //     if obj_container is NULL
    //         obj is the address of a C++ object
    //         increases C++(obj) refcnt (if it has one), and registers reference
    //     otherwise
    //         if indirection=0
    //             obj is the address inside container C++ object where the object resides
    //         otherwise
    //             obj is the address inside container C++ object where pointer to object resides
    //         increases obj_container reference
    // returns NULL in case of error but never deletes obj
    static python_metadata *new_python_metadata(void *obj, int indirection, python_metadata *obj_container,
						python_type_metadata *type, PyObject *ctor_args);

    // dealloc
    // call this function (also done by python interpreter) when ob_refcnt reaches 0
    // deregisters python reference
    // decreases C++ object refcnt (if it has one) and deletes C++ object if necesary
    // calls python_metadata destructor (makes a check on ob_refcnt)
    static void delete_python_metadata(python_metadata *python_obj);

    //! @name Reference counts (python)
    //@{
    inline void python_incref() {ob_refcnt++;}
    python_metadata *python_decref(); //@}

    //! @name Reference counts (C++)
    //@{
    int has_refcnt();
    int get_refcnt(); // check first with has_refcnt
    void c_incref();
    void *c_decref();
    static void c_incref(void *obj, python_type_metadata *type);
    static void *c_decref(void *obj, python_type_metadata *type); //@}

    //! @name Getters
    //@{
    void *get_obj();
    void *get_obj_woind(); // w/o ind-irection
    inline python_metadata *get_obj_container() {return obj_container;}
    pymd_cobj_type get_obj_type();
    int get_c_size();
    inline char *get_type_string() {return const_cast<char*>(ob_type->tp_name);}
    inline python_type_metadata *get_python_type() {return (python_type_metadata*)ob_type;}
    inline unsigned get_location() {return location;} // check get_obj_container()!=NULL first
    inline unsigned get_indirection() {return indirection;} //@} check get_obj_container()!=NULL first

    // conversions
    // convert to P3 or P4 (only changes the member variables, difficult stuff must be done outside)
    void transmute_object_type(unsigned indirection, unsigned location, python_metadata *obj_container);
    // convert to P1 or P2 (only changes the member variables, difficult stuff must be done outside)
    void transmute_object_type(void *obj);

    // checks
    // returns true and sets python error if object vanished
    bool check_vanished();
};

//! Python type object that describes a C++ class
class python_type_metadata
{
    // python type header (must be placed at class start)
    PyTypeObject to;

    // metadata
    int *data;
    char *doc;

    // ctor and dtor entry points
    int i_ctor;
    void *dtor;

    // reference count
    int refcnt_location;

    // method closures
    pymd_method_closure *method_closure;
    int method_closure_first;

    // vtable position
    void **vtable_position;

    // member and method rename
    char **rename;
    int n_rename, max_rename;

    // context for python module functions (that will get self=NULL in pyfunc_resolver)
    static python_type_metadata *functions_object_ctx;

    // PRIVATE FUNCTIONS
    char *create_name(char *name, char *namespc, char *module_name);
    char *create_private_name(char *name, char *module_name, int n_under=1);
    int inc_methods(int &n_tp_methods, int &max_tp_methods, int &n_method_closure, int &max_method_closure);
    bool call_prepickle_methods(PyObject *self);
    bool call_postunpickle_methods(PyObject *self);
    bool register_event_queue_methods(PyObject *self);

    // attributes
    pymd_attr_member parse_attr_member_string(char *attr_str);
    pymd_attr_method parse_attr_method_string(char *attr_str);
    bool parse_attr_bool(char *attr_str, const char *attr_name);
    PyObject *parse_attr_from_metadata(int *data);
    bool check_attributes(int verbo=0);

    // new, init, dealloc
    static int pyinit(PyObject *self, PyObject *args, PyObject *kwds);
    static void pydealloc(PyObject *self);
    static int pycompare(PyObject *a, PyObject *b);
    void cdealloc(void *obj);

    // python internal methods
    static PyObject *pygetstate(PyObject *self);
    static PyObject *pygetstate_gc(PyObject *self); // do not prepickle and returns nopickle members
    static PyObject *pysetstate(PyObject *self, PyObject *dict);
    static PyObject *pygetinitargs(PyObject *self);
    static PyObject *pyreduce(PyObject *self);
    static PyObject *pyreduce_ex(PyObject *self, PyObject *args);
    static PyObject *pygetmdattribute(PyObject *self, PyObject *args);
    static PyObject *pydebug(PyObject *self);
    static PyObject *pydebug_gc(PyObject *self);
    static PyObject *pydebug_ex(PyObject *self);
    static PyObject *pydebug_common(PyObject *self, int extra_info);
    static PyObject *pydebug_global(PyObject *self);
    static PyObject *pydebug_internal(PyObject *self);
    static void pygetstate_add_to_dict(PyObject *self, PyTypeObject *type, PyObject *dict, bool gc=false);
    static void pysetstate_get_from_dict(PyObject *self, PyTypeObject *type, PyObject *dict);

    // getsetters
    static int pygetset_common(PyObject *self, void *closure,
			       void **type_pointer, int **data,
			       int **pos_type_metadata, int n_indirections=1);
    static PyObject *pyget_member(PyObject *self, void *closure);
    static int pyset_member(PyObject *self, PyObject *value, void *closure);
    static PyObject *pyget_member_vector_n_max(PyObject *self, void *closure);
    static int pyset_member_vector_n_max(PyObject *self, PyObject *value, void *closure);
    static PyObject *pyget_member_object(PyObject *self, void *closure);
    static int pyset_member_object(PyObject *self, PyObject *value, void *closure);
    static int pyset_member_object(PyObject *self, PyObject *value, void *type_pointer, int *data);
    static PyObject *pyget_member_std_vector_impl(PyObject *self, void *closure);
    static int pyset_member_std_vector_impl(PyObject *self, PyObject *value, void *closure);
    static PyObject *pyget_constant(PyObject *self, void *closure);
    static int pyset_constant(PyObject *self, PyObject *value, void *closure);
    static PyObject *pyget_member_static(PyObject *self, void *closure);
    static int pyset_member_static(PyObject *self, PyObject *value, void *closure);

    // methods
    PYMD_GENERATE(PYMD_PROTO_DECLARATION, 0); // static number of methods!
    static PyObject *(*pyfunc[])(PyObject *self, PyObject *args);
    static PyObject *pyfunc_resolver(int nfunc, PyObject *self, PyObject *args);

    // util
    int get_n_getsetters();
    int search_method(char *method_name, int n_method);
    int search_method(const char *method_name); // used once tp_methods has the centinel
    int search_getsetter(char *getsetter_name);

    // rename
    char *rename_to_private(char *name);

    friend class python_metadata;
    friend class python_vector;
    friend class pymetadata_global;
    friend class pymetadata_gc;

public:
    python_type_metadata(char *module_name, int *data, char *doc, void *dl);
    ~python_type_metadata();
    static PyObject *pynew(PyTypeObject *type, PyObject *args, PyObject *kwds);

    // last class settings and PyType_Ready (must be called before add())
    int ready(int only_resolve_inheritance=0);

    // add to python context
    int add(PyObject *module);

    // reference counts
    void update_refcnt_from_bases(); // to be called after adding all types

    // base methods
    void update_methods(); // to be called after adding all types

    //! @name Getters
    //@{
    inline void **get_vtable() {return vtable_position;}
    inline char *get_name() {return const_cast<char*>(to.tp_name);}
    inline int has_getsetters() {return (to.tp_getset!=NULL);}
    inline int has_methods() {return (to.tp_methods!=NULL);}
    inline PyGetSetDef *get_getsetter(int i) {return &to.tp_getset[i];}
    inline PyMethodDef *get_method(int i) {return &to.tp_methods[i];} //@}

    // methods/functions
    int add_method(int *position, void *dl,
		   int &n_tp_methods, int &max_tp_methods,
		   int &n_method_closure, int &max_method_closure);
    void centinel_method(int &n_tp_methods, int &max_tp_methods);
    void set_context_object_for_functions(python_type_metadata *ctx); // to reuse methods implementation
    void *search_method_entry_point(const char *method_name); // only for non-static, 0 parameter methods
    int *get_data_of_member_at_location(int location);
    void *get_closure_of_member_with_data(int *data);

    // returns true if given_type is this one or one of its bases
    bool check_type_inheritance(python_type_metadata *given_type);

    // debug
    void dump(int extra_info=0, int level=0);
};

//@}
#endif

/* Offline metadata attributes:

   @metadata_attrs(
	pymd_cobj_type,
	pymd_attr_member,
	pymd_attr_method,
	pymd_method_closure,
	pymd_getset_closure,
	python_internal_special_type,
	python_metadata,
	python_type_metadata:
					~select
   )
*/
